import { useRef, useEffect, useState, useImperativeHandle, useCallback, Ref, forwardRef } from 'react'
import { OrbitControler } from './controler/OrbitControler'
import { Scene } from './core/Scene'
import { Vector2 } from './math/Vector2'
import { Group } from './objects/Group'
import { Img } from './objects/Img2D'
import { Object2D } from './objects/Object2D'

import './index.css'
import { Text2D, calcBaseLine, fontsInfo, measureTextSize, textSize } from './objects/Text2D'
import { ShapeControler } from './controler/shapeControler'
import { Shape2D } from './objects/Shape2D'
import { SelectShape } from './objects/ObjectUtils'
import { Matrix3 } from './math/Matrix3'

const IS_MAC = navigator.userAgent.indexOf('Mac OS') >= 0;

interface ObjKv{ 
    [key:string]:any
}
interface ImgEditorProps {
    size?: {
        width: number
        height: number
    },
    imgChange: (img: Shape2D | null) => any,

}

const canvasRect = {
    width: 0,
    height: 0,
    left: 0,
    top: 0,
}


export interface ImgEditorRef {
    native: HTMLCanvasElement,
    addImg: (img: HTMLImageElement, deleteable?: boolean, offset?: Vector2) => Img;
    changeLayerIndex: (index: number) => void
    removeImg: () => void;
    getImglist: () => Img[];
    snapShot: () => Promise<Blob>;
    setEditState: (state: EditState)=> any
    setShapeAttr:(opts:ObjKv)=> any
}

const m1 = new Matrix3(), m2 = new Matrix3() ;

function addOffset(mat3:Matrix3, ox:number,oy:number){
    m1.makeTranslation(-ox, -oy)
    m1.multiply(mat3);
    m1.multiply(m2.makeTranslation(ox,oy));
    return m1
}



const scene = new Scene()
let shapeHover: Shape2D | null;
const selectObj = SelectShape(scene)

/* 相机轨道控制器 */
const orbitControler = new OrbitControler(scene.camera)

/* 图案控制器 */
const shapeControler = new ShapeControler()
scene.add(shapeControler)



/* 图形集合 */
const shapeGroup = new Group()
scene.add(shapeGroup)
// offset 为鼠标相对画布元素的位置
function addImg(image: HTMLImageElement, deleteable = true, mp?: Vector2) {
    const { top, left, width, height } = canvasRect
    const wh = new Vector2(image.width, image.height);
    const ratio = canvasRect.width / Math.max(wh.x, wh.y)// 通用后应该为实时尺寸
    if (ratio < 1) {
        wh.multiplyScalar(ratio);

    }

    // 画布坐标系原点已移至中心
    const position = new Vector2(- wh.x, - wh.y).multiplyScalar(.5); //  假定图片中心就在画布中心 
    if (mp) {
        const center = new Vector2(left + width * .5, top + height * .5) // 画布中心的client 坐标
        const offset = mp.sub(center);// 鼠标位置相对画布中心的坐标
        position.add(offset); 
    }
    // // 只留一张产品图
    // if (!deleteable) {
    //     const ind = shapeGroup.children.findIndex((e) => !(e as Img).deleteable)
    //     if (ind > -1) {
    //         shapeGroup.children.splice(ind, 1);
    //         URL.revokeObjectURL((shapeControler.img?.image as HTMLImageElement).src);

    //     }
    // }
    const img = new Img({
        image,
        size: wh,
        position,
        offset: new Vector2(),
        name: 'img' + shapeGroup.children.length,
        style: {

        },
        deleteable
    })
    shapeGroup.add(img)
    

    shapeControler.img = img //  自动选中新加的图层
    scene.needUpdate = true;
    return img;
}

function swap(arr: any[], a: number, b: number) {
    const t = arr[a];
    arr[a] = arr[b];
    arr[b] = t
}

function changeLayerIndex(offset: number) {
    if (!offset) return;
    const imgs = shapeGroup.children, curImg = shapeControler.img!, index = curImg.index;
    let curInd = imgs.indexOf(curImg), temImg: Object2D;
    if (offset < -1) { //底层
        if (curInd === 0) return
        curImg.index = imgs[0].index - 1;
        shapeGroup.sort()
    } else if (offset > 1) {//顶层
        if (curInd === imgs.length - 1) return

        curImg.index = imgs[imgs.length - 1].index + 1;
        shapeGroup.sort()
    } else {
        const targetInd = curInd + offset;
        if (targetInd >= imgs.length || targetInd < -1) return
        temImg = imgs[targetInd];
        curImg.index = temImg.index;
        temImg.index = index;
        swap(imgs, curInd, targetInd);

    }
    scene.needUpdate = true


}
// 缓存需要计算文本的样式
const textstyle = { 
    font:'',
    lineHeight:''
}
// 我这个状态应该是即将添加新元素的状态 而不是选中图层的状态， 因为现在唯一比较特殊的图层就是文字， 其它的暂时不考虑 全当图片处理,和操作关联在一起的话，就不一样了
export type EditState = 'select' | 'text' | 'shape' | 'pen' | 'moveShape'|'moveCanvas'
const editMapCursor = { 
    select:'default',
    text: 'text',
    shape:'crosshair' ,
    pen :'crosshair' , //算了先这样
    moveCanvas:'grabbing',
    moveShape:'move'
}
const mouseClient = new Vector2();

let inited = false, editState: EditState = 'select';

let activeText2D: Text2D| null;


window.addEventListener('pointermove', (e) => {
    mouseClient.set(e.clientX, e.clientY);
})
let canvas: HTMLCanvasElement;




// 暂时采用 内外同时的方式 不可变状态这个东西，有点麻烦
export const ImgEditor = forwardRef(({ imgChange }: ImgEditorProps, ref: Ref<ImgEditorRef>) => {

    const [canvasInfo, updateCanvasInfo] = useState({
        width: 0,
        height: 0,
        left: 0,
        top: 0,
    });



    // 鼠标样式
    const [cursor, setCursor] = useState('default');
    const refTextEditor = useRef<HTMLTextAreaElement | null>(null) //为了让它自动获得焦点 还是需要元素实例的
    const canvasRef = useRef<HTMLCanvasElement | null>(null);
    const containerRef = useRef<HTMLDivElement | null>(null);

    function handleResize({ width, height, top, left }: DOMRect) {
        updateCanvasInfo({ width, height, left, top });
        canvasRect.width = width;
        canvasRect.height = height;
        canvasRect.left = left;
        canvasRect.top = top;
    }

    function updateMouseCursor() {
        // 改为经过图形时隐藏，  但是这样的话，离开图形得恢复之前的啊， 所以这里应该判断一下当前的state 还是需要一个映射 

        shapeControler.mouseState ? setCursor('none'):setCursor(editMapCursor[editState]) ;
    }
    /* 事件监听 */
    const init = useCallback((canvas: HTMLCanvasElement, textarea: HTMLTextAreaElement) => {
        /* 鼠标按下*/
        console.log('init')
        canvas.addEventListener('pointerdown', (event: PointerEvent) => {
            const { button, clientX, clientY, } = event
            const mp = scene.clientToClip(clientX, clientY)
            switch (button) {
                case 0:
                    shapeHover = selectObj(shapeGroup.children, mp)
                    if (shapeHover && shapeHover instanceof Text2D) {
                        activeText2D = shapeHover;// 更新用
                    }
                    if (shapeControler.pointerdown(shapeHover, mp)) {
                        imgChange(shapeControler.shape)
                    }
                    updateMouseCursor()

                    break
                case 1:
                    editState  ='moveCanvas';
                    setCursor('grab')
                    orbitControler.pointerdown(clientX, clientY)
                    break
            }
        })
        canvas.addEventListener('dblclick', (event) => {
            const { clientX, clientY } = event
            const mp = scene.clientToClip(clientX, clientY)
            const shapeHover = selectObj(shapeGroup.children, mp)
            if (activeText2D) {
                if (shapeHover === activeText2D) { //点击选中的文字元素
                    // editState='text' ;
                    const { textContent, position } = shapeHover;
                    refTextEditor.current && (refTextEditor.current.innerText = textContent);
                    // position 要换算为基于左上角的 再乘上视图 texPos似乎没必要了， 因为现在按矩阵来
                    const halfW =canvasRect.width *.5 , halfH = canvasRect.height* .5 ;
                    const textPos = new Vector2(position.x + halfW, position.y + halfH );
                    
                    // 换这个
                    // const {a,b,c,d,e,f} = activeText2D.canvasTransform ;
                    const cssTransform = activeText2D.pvmMatrix.cssMatrix;
                    
                    // 现在考虑 把开始位移也加上，保证 矩阵变换是完全一样的
                    // addTextEditor(textPos.x, textPos.y, `matrix(${a},${b},${c},${d},${e},${f})`) // 现在画布上无遮挡 ，可以直接使用这个
                    addTextEditor(textPos.x, textPos.y, `${cssTransform}`) // 


                }
            }
        })

        /* 鼠标移动 */
        canvas.addEventListener('pointermove', (event: PointerEvent) => {
            const { clientX, clientY } = event
            const mp = scene.clientToClip(clientX, clientY)
            if (orbitControler.panning) {
                orbitControler.pointermove(event.clientX, event.clientY)
            }
            // console.log('pointermove')

            shapeControler.pointermove(mp)
            updateMouseCursor()
        })

        /* 鼠标抬起 */
        window.addEventListener('pointerup', (event: PointerEvent) => {
            switch (event.button) {
                case 0:
                    if( shapeControler.shape){ 
                        // const {pvmMatrix,scale, position, rotate}  =shapeControler.shape;

                    }
                    shapeControler.pointerup()
                    break
                case 1:
                    editState = 'select'
                    setCursor('auto')
                    orbitControler.pointerup()
                    break
            }
        })

        /* 键盘按下 */
        window.addEventListener(
            'keydown',
            (e: KeyboardEvent) => {
              const { key, altKey, shiftKey, ctrlKey, metaKey } =e;
                shapeControler.keydown(key, altKey, shiftKey)
                if (key === 'Enter' || key === 'Escape') {
                    updateMouseCursor()
                }
                const ctrl = IS_MAC ?metaKey: ctrlKey;
                switch (key) {
                    case 'b':
                        editState = 'pen';
                        setCursor(`url()`)// 一张笔的图片
                        break;
                    case 't':
                        editState = 'text'
                        shapeControler.shape = null ;
                        activeText2D =null;
                        scene.needUpdate = true;
                        setCursor('text')
                        break;
                    case 'Escape':
                        editState = 'select'
                        setCursor('select');
                        break;
                    case '=' : 
                    if( ctrl){ 
                        e.preventDefault();
                        orbitControler.doScale(-1)//只看大于小于0

                    }
                    break;
                    case '-' : 
                    if( metaKey){ 
                        e.preventDefault();
                        orbitControler.doScale(1)//只看大于小于0

                    }

                    break;
                    default:
                        break;
                }
            }
        )

        /* 键盘抬起 */
        window.addEventListener('keyup', ({ altKey, shiftKey }: KeyboardEvent) => {
            shapeControler.keyup(altKey, shiftKey)
        })

        /* 滑动滚轮缩放 */
        canvas.addEventListener('wheel', (event) => {
            event.preventDefault()

            orbitControler.doScale(event.deltaY)
        })
        canvas.addEventListener('mouseleave', () => {
            shapeControler.mouseState = null;
            scene.needUpdate = true
        })

        //  粘贴 拖放图片 暂时不考虑多个文件

        canvas.addEventListener('dragover', (e) => e.preventDefault());
        canvas.addEventListener('drop', (e) => {
            e.preventDefault()// 默认事件是打开文件
            const file = e.dataTransfer?.files[0];
            if (file && (/image\/*/).test(file.type)) {
                const img = new Image();
                // console.log(file.name, file.type)
                img.src = URL.createObjectURL(file);
                img.onload = () => {
                    addImg(img, true, new Vector2(e.clientX, e.clientY));
                }

            }
        })

        // 文本域尺寸同步

        textarea.addEventListener('blur', (e) => {
            addText2D(e);
        })
        textarea.addEventListener('focus', () => {
            const style = getComputedStyle(textarea);
        //   缓存
            textstyle.font = style.font;
            textstyle.lineHeight = style.lineHeight;
        })
        textarea.addEventListener('input', (e) => {
            // input 事件的ts 类型有些问题
            measureTextSize(textstyle.font, textstyle.lineHeight,textarea.value );
            // requestAnimationFrame(()=> {
            //     textarea.style.height = textSize.height +'px';
            //     textarea.style.width = textSize.width +'px';
            //  })

        })

        textSize.onChange= ()=> { 
            textarea.style.height = textSize.height +'px';
            textarea.style.width = textSize.width +'px';
         
        }
        





        // canvas 似乎触发不了 真的要可编辑元素才行？ 这个事件没有鼠标参数 所以必须要维护一个全局的鼠标位置
        window.addEventListener('paste', (e) => {
            e.preventDefault();// 默认会粘贴到编辑区域中 否则什么也不会发生
            const file = e.clipboardData?.files[0];
            if (file && (/image\/*/).test(file.type)) {
                const img = new Image();

                img.src = URL.createObjectURL(file);
                img.onload = () => {
                    addImg(img, true,);
                }
                //似乎只能用 filereader 转 dataurl 是ts的问题 

            }
        })

        window.addEventListener('resize', () => {
            if (canvas) {
                handleResize(canvas.getBoundingClientRect())
            }
        })





        /* 按需渲染 */
        orbitControler.addEventListener('change', () => {
            scene.needUpdate = true
        })
        shapeControler.addEventListener('change', () => {
            scene.needUpdate = true
        })

        /* 渲染 */
        scene.loop();
        scene.needUpdate = true
    }, [])





    useImperativeHandle(ref, () => ({
        native: canvasRef.current!,
        addImg(...args) {
            const img = addImg(...args);
            imgChange(img);
            return img
        },
        removeImg() {
            shapeControler.img?.remove();
            shapeControler.img = null;
        },
        changeLayerIndex,
        getImglist(): Img[] {
            return shapeGroup.children as Img[];
        },
        snapShot() {
            shapeControler.visible = false;
            scene.needUpdate = true;
            return new Promise((res) => {

                requestAnimationFrame(() => {
                    canvasRef.current?.toBlob((blob) => {
                        res(blob!);
                        shapeControler.visible = true;
                    },)
                })

            })
        },
        setEditState(state){ 
            editState = state;
            switch (state) {
                case 'select':
                    setCursor('defualt')
                    
                    break;
                case 'text':
                  setCursor('text')
                  shapeControler.shape = null ;
                  activeText2D =null;
                  scene.needUpdate = true;
                        break;
                
                default:
                    break;
            }
        },// 外部修改图形的 颜色 样式 等 目前，主要是文字特殊一些需要计算出font ,还是说应该把合成font的方法提供出去 文字果然还是需要特殊处理，至少改变字号的时候，需要重新计算尺寸
        setShapeAttr(opts:ObjKv){ 
            const shape = shapeControler.shape;
            if( shape){ 

                Object.entries(opts).forEach(([key, val])=> { 
                    shape[key] = val ;
                })
                scene.needUpdate = true;
            }
        }
    }))

    useEffect(() => {
        if (!inited && canvasRef.current && refTextEditor.current) {
            canvas = canvasRef.current;
            scene.setOption({ canvas })
            init(canvas, refTextEditor.current);
            handleResize(canvas.getBoundingClientRect())
            inited = true; //不知道什么原因会触发两次  是因为开发模式吗
        }

    }, [canvasRef])


    // 添加一个文本框之后 ，文本框获得焦点   回退至选择模式
    function addTextEditor(x: number, y: number, matrix?: string) {
        const el = refTextEditor.current;
        if (el) {
            el.style.zIndex = '0';
            el.style.opacity = '1'
            el.style.top = y + 'px';
            el.style.left = x + 'px';
            if (activeText2D) {

                activeText2D.visible = false;
                shapeControler.shape = null;
                shapeControler.visible = false;
                
                scene.needUpdate = true;
                el.value = activeText2D.textContent;
                el.style.width  = activeText2D.size.width + 'px';
                el.style.height  = activeText2D.size.height + 'px';
                setCursor('auto')
            }
            el.focus()
            editState = 'select';
            
            // 初次添加输入框，如果要考虑视图投影矩阵再说
            

            if (matrix) {
                el.style.transform = matrix;
                let x =0 + canvasRect.width * .5 , y = 0 +canvasRect.height * .5 ;
                if( activeText2D && activeText2D.originChanged){ 
                    x -=activeText2D.size.x /2 ;
                    y -=activeText2D.size.y /2 ;
                }
                el.style.left = x + 'px';
                el.style.top = (y) + 'px';
   
                el.select()
            }

        }

    }
    // 文本输入失焦后触发
    function addText2D(e: FocusEvent) {
        // innerText 应该是包含换行符空格
        shapeControler.visible = true;
        const el = e.target as HTMLTextAreaElement;
        if (!el.value.trim()) {
            editState = 'select';
            setCursor('default');
            el.style.zIndex = `-1000`;
            return;
        }
        //  后面应该是用内存数据 控制 dom  和canvas ，到时候 除了尺寸外都不需要从元素上获取，
        el.style.transform = 'none'
        const { left, top, width, height } = el.getBoundingClientRect();
        const x = left - canvasRect.left - canvasRect.width/2, y = top - canvasRect.top - canvasRect.height/2;//position 决定了落笔的基准点  图案左上角相对画布中心的位置
        const style = getComputedStyle(el);
        const { emSize, descend, ascend } = fontsInfo.songti
        const lineHeight = parseInt(style.lineHeight), fontSize = parseInt(style.fontSize),
            font = style.font,
            textAlign = style.textAlign,
            baseLine = calcBaseLine(lineHeight, fontSize, emSize, ascend, descend);;
        // font直接从style上获取 ，避免手动拼接的问题
        const horizontalOffset =
            textAlign === "center"
                ? width / 2
                : textAlign === "right"
                    ? width
                    : 0;
        const textContent = el.value;
        const mat3 = scene.camera.pvMatrix, mat3Inv = scene.camera.pvMatrix.invert();
        //   现在这里得考虑视图投影矩阵了   之前添加图片那里也应该考虑的。 
        //position 是 图案左上角相对画布中心的位置
        const position = new Vector2(x,y).applyMatrix3(mat3Inv);
        const size = new Vector2(width, height);
        if (activeText2D) {
            // activeText2D.position.copy(position)
            activeText2D.size.copy(size);
            activeText2D.font = font;
            activeText2D.horizontalOffset = horizontalOffset;
            activeText2D.textAlign = textAlign;
            activeText2D.baseLine = baseLine;
            activeText2D.textContent = textContent;
            activeText2D.content = textContent.split('\n'); // 这里不知道为什么推断不出正确的类型
            activeText2D.visible = true;
            // 现在没有什么缓存机制，不然这里应该加一个needupdate

        } else {
// 这里文本的样式应该沿用之前的
            const text2D = new Text2D({
                position,
                size,
                font,
                fontSize:14,
                
                horizontalOffset,
                textAlign: style.textAlign,
                baseLine,
                textContent,
                content: textContent.split('\n')

            })

            shapeGroup.add(text2D);
        }
        scene.needUpdate = true;
        editState = 'select';

        setCursor('default');
        el.style.zIndex = `-1000`;
        el.style.opacity = '0'
        el.value = ''
    }




    //  新增元素的事件处理
    function handleCanvasClick(e: React.MouseEvent) {
        if (shapeControler.shape) return;
        switch (editState) {
            case 'text':
                const pos = new Vector2(e.clientX - canvasRect.left, e.clientY - canvasRect.top)
                addTextEditor(pos.x -10, pos.y - 10);
                break;

        }
    }



    // 这里希望是跟随外部盒子的高宽， 或者给定一个尺寸。
    return <div ref={containerRef} onClick={handleCanvasClick} className='canvas-wraper' style={{ width: '100%', minHeight: '512px' }}>
        {/* 我都想直接操作dom 了 反正要拿实例 */}
        <textarea ref={refTextEditor}
            className='text-input textarea'
        >
        </textarea>



        <canvas
            ref={canvasRef}
            style={{ cursor: cursor }}
            width={canvasInfo.width}
            height={canvasInfo.height}
            className='canvas2d'
        ></canvas>
    </div>

})